home *** CD-ROM | disk | FTP | other *** search
/ Xentax forum attachments archive / xentax.7z / 23817 / FMO Model Converter 2.0.py.7z / FMO Model Converter 2.0.py
Encoding:
Text File  |  2023-01-01  |  7.6 KB  |  231 lines

  1. import re
  2.  
  3. class Variables: # A container for variables that need to be passed between functions. 
  4.     MeshArray = list()
  5.     VertArray = list()
  6.     UVArray = list()
  7.     UVPadding = 0
  8.     FaceVerts = 0
  9.     FaceBytes = 0
  10.     FacePadding = 0
  11.     FaceCount = 0
  12.     VertCount = 0
  13.     Verts = 0
  14.     HeaderPadding = 0
  15.  
  16.  
  17.  
  18.  
  19.  
  20. def Header_Parser():
  21.     file.seek(-7,1) # Jump back to important header bytes.
  22.     byte = file.read(1) # This byte identifies the mesh type.
  23.  
  24.     if byte == b'\x12': # 'Loose' byte triangles. 
  25.         vars.FaceVerts = 3
  26.         vars.FaceBytes = 1
  27.         vars.FacePadding = 3
  28.         vars.HeaderPadding = 6
  29.         vars.UVPadding = 6
  30.        
  31.     elif byte == b'\x14': # Byte faces.
  32.         vars.FaceVerts = 4
  33.         vars.FaceBytes = 1
  34.         vars.FacePadding = 4
  35.         vars.HeaderPadding = 8
  36.         vars.UVPadding = 8
  37.         
  38.     elif byte == b'\x11': # 'Loose' short triangles.
  39.         vars.FaceVerts = 3
  40.         vars.FaceBytes = 2
  41.         vars.FacePadding = 6
  42.         vars.HeaderPadding = 8
  43.         vars.UVPadding = 8        
  44.  
  45.     elif byte == b'\x13': # Short faces.
  46.         vars.FaceVerts = 4
  47.         vars.FaceBytes = 2
  48.         vars.FacePadding = 8
  49.         vars.HeaderPadding = 8
  50.         vars.UVPadding = 8        
  51.  
  52.     vars.FaceCount = int.from_bytes(file.read(2), 'little') # Get number of faces following header.
  53.     file.seek(4 + vars.HeaderPadding,1) # Skip rest of header plus padding.
  54.  
  55.  
  56.  
  57.  
  58.  
  59. def Face_Parser():
  60.     CurrentMesh = len(vars.MeshArray) - 1 # Get the newest entry in the mesh array. 
  61.     vars.MeshArray[CurrentMesh].append([0,0,0]) if vars.FaceVerts == 3 else vars.MeshArray[CurrentMesh].append([0,0,0,0]) # Create a new entry within it.
  62.     CurrentFaceIndex = len(vars.MeshArray[CurrentMesh]) - 1 # Get this new entry.
  63.     
  64.     for x in range(vars.FaceVerts):    
  65.         vars.MeshArray[CurrentMesh][CurrentFaceIndex][x] = int.from_bytes(file.read(vars.FaceBytes), 'little') # Write verts to the current face index.   
  66.  
  67.         if vars.MeshArray[CurrentMesh][CurrentFaceIndex][x] >= vars.VertCount: vars.VertCount = vars.MeshArray[CurrentMesh][CurrentFaceIndex][x] + 1 # Get total number of verts from largest face vertex. Must add one for accurate count.   
  68.         
  69.     file.seek(vars.FacePadding, 1) # Skip padding between the face and its UVs.
  70.  
  71.  
  72.  
  73.  
  74.  
  75. def UV_Parser():
  76.     CurrentMesh = len(vars.UVArray) - 1 # Get the newest entry in the UV array.
  77.  
  78.     for x in range(vars.FaceVerts): # Every vert has a UV.
  79.         vars.UVArray[CurrentMesh].append([0,0]) # Create a new entry within it.
  80.         CurrentUVIndex = len(vars.UVArray[CurrentMesh]) - 1 # Get this new entry.
  81.  
  82.         # Get and store the UV coordinates.
  83.         vars.UVArray[CurrentMesh][CurrentUVIndex][0] = int.from_bytes(file.read(2), "little", signed = "True") / 4096
  84.         vars.UVArray[CurrentMesh][CurrentUVIndex][1] = 1 - (int.from_bytes(file.read(2), "little", signed = "True") / 4096)
  85.  
  86.  
  87.  
  88.  
  89.  
  90. def Vert_Parser():
  91.     FloatStart = file.tell() - 16 # Get back to the start of the vert block.  
  92.     FloatCount = 0
  93.     CoordPadding = CoordBytes =  2
  94.     line = file.read(16)
  95.     
  96.     while re.search(b'....\x00\x00\x80\?', line) != None: # Skip floats that precede signed shorts.
  97.         FloatCount += 1
  98.  
  99.         if FloatCount > 50: # Certain models uses signed floats instead of signed shorts, a large number of floats is a clear indication.
  100.             file.seek(FloatStart, 0)
  101.             CoordBytes = CoordPadding = 4            
  102.             break
  103.  
  104.         line = file.read(16) 
  105.  
  106.     if FloatCount < 50: file.seek(-16, 1) # Jump back to the start of the first signed short.
  107.         
  108.     for x in range(vars.VertCount): # Write the X, Y, Z coordinates to the vert array, adjust them for proper scale.
  109.         vars.VertArray.append([0, 0, 0])
  110.  
  111.         for y in range(3): vars.VertArray[x][y] = (int.from_bytes(file.read(CoordBytes), "little", signed = "True") / 256) * 0.313
  112.         file.seek(CoordPadding, 1) # Skip padding between eacg coordinate.
  113.  
  114.  
  115.  
  116.  
  117.  
  118. def Face_Writer():
  119.     FaceVerts = len(vars.MeshArray[0][0]) # Determine if the mesh uses tris or faces.
  120.  
  121.     for x in range(len(vars.MeshArray[0])): # For entries in the current mesh.
  122.  
  123.         obj.write('f ')
  124.         
  125.         for y in range(3): # Incrementally write three verts and UVs.
  126.             vars.Verts += 1
  127.             obj.write(str(vars.Verts) + '/' + str(vars.Verts) + ' ')
  128.  
  129.         obj.write('\n')
  130.  
  131.         if FaceVerts == 4:
  132.             obj.write('f ')
  133.  
  134.             vars.Verts += 1
  135.  
  136.             for z in range(3): # Decrement to properly form the other face.
  137.                 obj.write(str(vars.Verts) + '/' + str(vars.Verts) + ' ')
  138.                 vars.Verts -= 1
  139.  
  140.             
  141.             # Increment to begin the next face.
  142.             vars.Verts += 3
  143.             obj.write('\n')   
  144.  
  145.  
  146.  
  147.  
  148.  
  149. def UV_Writer():
  150.     CurrentMeshLength = len(vars.UVArray[0]) # Get thhe number of UVs for the current mesh.
  151.     
  152.     for x in range(CurrentMeshLength): 
  153.         obj.write("vt ")
  154.  
  155.         for y in range(2):
  156.             obj.write(str(vars.UVArray[0][x][y]) + ' ')
  157.  
  158.         obj.write('\n')   
  159.  
  160.  
  161.  
  162.  
  163.  
  164. def Vert_Writer():
  165.     CurrentMeshLength = len(vars.MeshArray[0]) # Get the length of the current mesh.
  166.     FaceVertCount = len(vars.MeshArray[0][0]) # Determine if the mesh uses tris or faces.
  167.  
  168.     for x in range(CurrentMeshLength): 
  169.  
  170.         for y in range(FaceVertCount):
  171.             obj.write("v ")
  172.  
  173.             for z in range (3):
  174.                 obj.write(str(vars.VertArray[vars.MeshArray[0][x][y]][z]) + " ") # Write the individual coordinates
  175.  
  176.             obj.write("\n")
  177.  
  178.  
  179.  
  180.  
  181.  
  182. components = 1
  183. vars = Variables()
  184.  
  185. for i in range(components): # Certain model types (legs, tanks, etc) store parts of the model sequentially, rather than in one large block.
  186.  
  187.     with open('', 'rb') as file:
  188.  
  189.         bytes = file.read(8)
  190.  
  191.         while re.fullmatch(b'[\x01-\xff][\x01-\xff][\x01-\xff][\x01-\xff]\x00\x00\x80\?', bytes) == None: # Search for headers until vert block is reached. 
  192.  
  193.             if re.fullmatch(b'[\x00-\x09]\x12[\x01-\xff][\x00-\xff][\x00-\x09]\x00[\x00-\x09]\x00', bytes) != None or re.fullmatch(b'[\x00-\x09]\x14[\x01-\xff][\x00-\xff][\x00-\x09]\x00[\x00-\x09]\x00', bytes) != None or re.fullmatch(b'[\x00-\x09]\x11[\x01-\xff][\x00-\xff][\x00-\x09]\x00[\x00-\x09]\x00', bytes) != None or re.fullmatch(b'[\x00-\x09]\x13[\x01-\xff][\x00-\xff][\x00-\x09]\x00[\x00-\x09]\x00', bytes) != None: #Mesh header formats.
  194.  
  195.                 #Create a new entry in the mesh and UV array for the new object.    
  196.                 vars.MeshArray.append([])  
  197.                 vars.UVArray.append([])
  198.  
  199.                 Header_Parser()
  200.  
  201.                 for x in range(vars.FaceCount):
  202.                     Face_Parser()
  203.                     UV_Parser()
  204.  
  205.                     if x != vars.FaceCount - 1: file.seek(vars.UVPadding, 1) #UVs padded until the last set.  
  206.  
  207.             file.seek(-7,1) # Progress by one to ensure nothing is missed.
  208.             bytes = file.read(8)
  209.  
  210.         Vert_Parser()
  211.         
  212.     with open('', 'w') as obj: # With all the data parsed, move on to writing it in the proper order. 
  213.  
  214.         MeshCount = 1
  215.  
  216.         for x in range(len(vars.MeshArray)):
  217.  
  218.             obj.write('object Mesh ' + str(MeshCount) + '\n') # Separate the meshes.
  219.  
  220.             Vert_Writer()
  221.             UV_Writer()
  222.             Face_Writer()
  223.             
  224.             # Remove the heads of these arrays as they're no longer needed. 
  225.             vars.MeshArray.pop(0)
  226.             vars.UVArray.pop(0)
  227.  
  228.             MeshCount = MeshCount + 1
  229.  
  230.     vars.VertArray.clear() # Clear the vert array once all data written. 
  231.